<?php

/**
 * @param  int           $code           代码
 * @param  string|array  $msg            信息(msg)或数据(data)
 * @param  array         $extra          额外数据
 * @param  int           $response_code  自定义响应代码
 *
 * @return array
 */
function msg ($code, $msg = null, $extra = [], $response_code = 200) {
	$arr = ['code' => $code];
	if (null !== $msg) {
		if (is_array($msg) || is_object($msg))
			$arr['data'] = $msg;
		else $arr['msg'] = $msg;
	}
	if (!empty($extra) && is_array($extra))
		$arr = array_merge($arr, $extra);

	if (is_int($response_code) && $response_code !== 200)
		http_response_code($response_code);

	return $arr;
}

/**
 * @param  int           $code           代码
 * @param  string|array  $msg            信息(msg)或数据(data)
 * @param  array         $extra          额外数据
 * @param  int           $response_code  自定义响应代码
 *
 * @return string
 */
function jsonMsg ($code, $msg = null, $extra = [], $response_code = 200) {
	return arr2json(msg($code, $msg, $extra, $response_code));
}

/**
 * @param  array  $arr
 * @param  bool   $pre
 *
 * @return string
 */
function arr2json ($arr = [], $pre = false) {
	return str_replace('\\/', '/', version_compare(phpversion(), '5.4.0', '<')
		? unicode2str(json_encode($arr))
		: json_encode($arr,
			$pre ? JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES : JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}

/**
 * 数组转http表单
 *
 * @param  array<string,string|int>  $params     请求参数数组
 * @param  bool                      $en_char
 * @param  string                    $separator  多个参数连接符
 * @param  string                    $splice     参数键值连接符
 *
 * @return false|string
 */
function arr2params ($params, $en_char = false, $separator = '&', $splice = '=') {
	if (!is_array($params)) return false;

	$arr = [];
	foreach ($params as $key => $val) {
		if ($en_char !== false) {
			if ($en_char === true) $en_char = '+';
			else $en_char = strval($en_char);
			$key = str_encode($key, $splice . $separator . $en_char);
			$val = str_encode($val, $separator . $en_char);
		}

		$arr[] = $key . $splice . $val;
	}

	return implode(strval($separator), $arr);
}

/**
 * [array2tree 一维数组转父子结构多维数组]
 *
 * @param  array                                                   $list    [一维数组数据]
 * @param  array{id:string,pId:string,child:string,root:null|int}  $option  [默认父子字段名称、顶级id等选项]
 *
 * @return array          [父子结构多维数组]
 */
function array2tree ($list, $option = []) {
	$opt = [
		'id'    => 'id',
		'pId'   => 'pId',
		'child' => 'child',
		'root'  => null
	];
	if (is_array($option)) $opt = array_merge($opt, $option);

	$tree = [];
	foreach ($list as $data) {
		if ($data[$opt['pId']] === $opt['root']) {
			$child = array2tree($list, ['root' => $data[$opt['id']]]);
			if ($child) $data[$opt['child']] = $child;
			$tree[] = $data;
		}
	}

	return $tree;
}

/**
 * 二维数组根据某个字段排序
 *
 * @param  array   $arr   要排序的数组
 * @param  string  $key   要排序的键字段
 * @param  string  $sort  排序类型  SORT_ASC     SORT_DESC
 *
 * @return array 排序后的数组
 */
function arr_multisort ($arr, $key, $sort = SORT_ASC) {
	$keysValue = arr_column($arr, $key);
	array_multisort($keysValue, $sort, $arr);

	return $arr;
}

/**
 * 重写array_column函数
 *
 * @param  array|\stdClass  $array
 * @param  int|null|string  $column_key
 * @param  int|null|string  $index_key
 * @param  bool             $recursive  相同键名的值递归组成一个数组
 *
 * @return array
 */
function arr_column ($array, $column_key, $index_key = null, $recursive = false) {
	if ($array instanceof stdClass) $array = (array)$array;
	if (!is_array($array)) return [];

	$arr = $count = [];
	foreach ($array as $v) {
		if ($v instanceof stdClass) $v = (array)$v;
		$value = @$v[$column_key];
		if ($index_key === null) {
			$arr[] = $value;
			continue;
		}

		if (!isset($v[$index_key])) continue;
		else if ($recursive && array_key_exists($v[$index_key], $arr)) {
			if ($count[$v[$index_key]] > 1) $arr[$v[$index_key]][] = $value;
			else $arr[$v[$index_key]] = [$arr[$v[$index_key]], $value];
		} else $arr[$v[$index_key]] = $value;

		if (!array_key_exists($v[$index_key], $count)) $count[$v[$index_key]] = 0;
		$count[$v[$index_key]]++;
	}

	return $arr;
}

/**
 * 同java  System.arraycopy
 *
 * @param  array  $src      原数组
 * @param  int    $srcPos   原数组的起始索引
 * @param  array  $dest     目标数组
 * @param  int    $destPos  目标数组起始索引
 * @param  int    $length   要复制的数组的长度
 *
 * @return void
 */
function array_copy ($src, $srcPos, &$dest, $destPos, $length) {
	array_splice($dest, $destPos, $length, array_slice($src, $srcPos, $length));
}

/**
 * 是否关联(键值对)数组
 *
 * @param  array  $arr
 *
 * @return bool
 */
function array_is_associative ($arr) {
	return !empty($arr) && array_keys($arr) !== range(0, count($arr) - 1);
}

/**
 * @param  int|string|array|object  $data
 *
 * @return bool
 */
function is_empty ($data) {
	if (is_array($data)) return empty($data);

	if (is_object($data)) return empty((array)$data);

	if (null === $data || '' === $data) return true;

	return false;
}

/**
 * [size_format 大小单位自动转换]
 *
 * @param  int     $size       字节大小
 * @param  string  $separator  连接符
 *
 * @return int|string
 */
function size_format ($size, $separator = ' ') {
	$units  = str_split(' KMGTPEZYBNDC');
	$factor = floor((strlen($size) - 1) / 3);

	return ($size >= 1024) ? sprintf("%.2f", $size / pow(1024, $factor)) . $separator . @$units[$factor] : $size;
}

/**
 * 获取13位时间戳
 *
 * @param  int  $len  长度 10-18，常用10和13
 *
 * @return int
 */
function longTime ($len = 13) {
	return intval(microtime(true) * pow(10, $len - 10));
}

/**
 * 随机字符串
 *
 * @param  int  $len  长度
 *
 * @return string
 */
function randKey ($len) {
	$chars = str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
	$max   = strlen($chars) - 1;
	$key   = '';
	for ($i = 0; $i < $len; $i++) $key .= $chars[mt_rand(0, $max)];

	return $key;
}

function uuid () {
	$md5 = md5(uniqid(mt_rand(), true));

	return preg_replace('/^(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})$/', '${1}-${2}-${3}-${4}-${5}', $md5);
}

/**
 * 获取客户端真实ip地址
 *
 * @param  bool  $proxy  是否获取代理ip地址
 *
 * @return string
 */
function getip ($proxy = false) {
	if (!$proxy) return $_SERVER['REMOTE_ADDR'];

	if (isset($_SERVER['HTTP_CLIENT_IP']))
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	else if (isset($_SERVER['HTTP_FORWARDED']))
		$ip = $_SERVER['HTTP_FORWARDED'];
	else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
	else
		$ip = $_SERVER['REMOTE_ADDR'];

	return $ip;
}

// session 操作封装
function session (...$args) {
	if (session_status() !== PHP_SESSION_ACTIVE || session_id() === '') @session_start();

	$length = count($args);
	if ($length > 1)
		if ($args[1] === null || $args[1] === '')
			unset($_SESSION[$args[0]]);
		else
			$_SESSION[$args[0]] = $args[1];
	else if (is_array($args[0]))
		foreach ($args[0] as $k => $v) {
			if ($v === null || $v === '')
				unset($_SESSION[$k]);
			else
				$_SESSION[$k] = $v;
		}
	else
		return @$_SESSION[$args[0]];

	return true;
}

/**
 * cookie 操作封装
 *
 * @param  string                                                                          $name
 * @param  false|null|string                                                               $value
 * @param  null|int|array{expire:int,path:string,domain:string,secure:bool,httponly:bool}  $expires_or_options
 *
 * @return string|true
 */
function cookie ($name, $value = false, $expires_or_options = []) {
	if ($value === false) return @$_COOKIE[$name];

	$opt = [
		'expire'   => 0,
		'path'     => '/',
		'domain'   => '',
		'secure'   => false,
		'httponly' => false
	];
	if (is_array($expires_or_options)) $opt = array_merge($opt, $expires_or_options);
	else $opt['expire'] = intval($expires_or_options);

	$value = strval($value);
	if ($value === '') $opt['expire'] = time() - 86400;
	else if (is_numeric($opt['expire']) && $opt['expire'] !== 0) $opt['expire'] = time() + $opt['expire'];

	return setcookie($name, $value, ...array_values($opt));
}

/**
 * 删除目录
 *
 * @param  string  $dir   [目录路径]
 * @param  bool    $self  [是否删除自身]
 */
function del_dir ($dir, $self = true) {
	array_map(function ($path) {
		$name = basename($path);
		if ('.' === $name || '..' === $name)
			return;
		if (is_dir($path)) {
			del_dir($path, false);
			@rmdir($path);
		} else if (!@unlink($path) && chmod($path, 0777))
			@unlink($path);
	}, glob($dir . DIRECTORY_SEPARATOR . '{.*,*}', GLOB_BRACE));
	if ($self && file_exists($dir))
		@rmdir($dir);
}

/**
 * unicode转字符串
 *
 * @param  string  $str
 *
 * @return string
 */
function unicode2str ($str) {
	return preg_replace_callback("#\\\u([0-9a-f]{4})#i", function ($r) {
		return iconv('UCS-2BE', 'UTF-8', pack('H4', $r[1]));
	}, $str);
}

/**
 * 自动编码转换
 *
 * @param  string  $str
 * @param  string  $charset
 *
 * @return string
 */
function charset ($str, $charset = 'UTF-8') {
	if ($charset === null || $charset === '')
		return $str;

	$encode = mb_detect_encoding($str, ["CP936", "ASCII", "GB2312", "GBK", 'UTF-8', 'BIG5']);
	if ($encode === false || $encode === strtoupper($charset))
		return $str;

	if ($encode === 'CP936')
		$str = iconv('UTF-8', 'latin1//IGNORE', $str);
	else
		$str = mb_convert_encoding($str, $charset, $encode);

	return $str;
}

/**
 * 取url中的文件格式
 *
 * @param  string  $url
 * @param  bool    $dot  是否包含点号，默认：假
 *
 * @return string
 */
function ext ($url, $dot = false) {
	if (null !== ($path = parse_url($url, PHP_URL_PATH)) && preg_match('/.*\.([^.]+)$/', basename($path), $m))
		return $dot ? '.' . $m[1] : $m[1];

	return '';
}

/**
 * 时长格式化
 *
 * @param  int|string  $duration
 *
 * @return false|string
 */
function duration ($duration) {
	if (!is_numeric($duration)) return false;
	$duration = intval($duration);
	if ($duration < 0) return false;

	if ($duration < 60) $format = 's秒';
	else if ($duration < 60 * 60) $format = 'i分s秒';
	else if ($duration < 60 * 60 * 24) $format = 'H时i分s秒';
	else if ($duration < 60 * 60 * 24 * 30) $format = 'd日 i分s秒';
	else if ($duration < 60 * 60 * 24 * 30 * 12) $format = 'm月d日 H时i分s秒';
	else $format = 'Y年m月d日 H时i分s秒';

	return date($format, $duration);
}

/**
 * 01:02:03 转 01时02分03秒
 *
 * @param  string  $duration  01:02:03
 *
 * @return false|string
 */
function duration2 ($duration) {
	if (!preg_match('/^(((\d+):)?(\d+):)?(\d+(\.\d*)?)$/', $duration, $m))
		return false;

	return duration(mktime($m[3] === '' ? 0 : $m[3], $m[4] === '' ? 0 : $m[4], $m[5]) - mktime(0, 0, 0));
}

/**
 * 纯数字转单位数字
 *
 * @param  int     $number
 * @param  string  $separator
 *
 * @return string
 */
function number_unit ($number, $separator = ' ') {
	$units  = ['', '万', '亿', '万亿'];
	$factor = floor((strlen($number) - 1) / 4);

	return $number < 10000 ? $number : sprintf("%.2f", $number / pow(10000, $factor)) . $separator . @$units[$factor];
}

/**
 * 格式化JSON字符串
 *
 * @param  string  $json    待格式化的json字符串
 * @param  string  $indent  缩进字符
 * @param  string  $wrap    换行符
 *
 * @return string 格式化后的json字符串
 */
function format_json ($json, $indent = "\t", $wrap = "\n") {
	$indent_count = 0;
	$result       = '';
	$inquote      = false;
	$ignore_next  = false;
	for ($i = 0; $i < strlen($json); $i++) {
		$char = $json[$i];
		if ($ignore_next) {
			// 忽略/前面的\
			if ($char === '/') $result = substr($result, 0, -1);
			$result      .= $char;
			$ignore_next = false;
			continue;
		}

		if ($char === '\\') $ignore_next = true;
		if ($char !== '"' && $inquote) { // 在字符串内部
			$result .= $char;
			continue;
		}

		// 忽略字符串外部的某些字符
		if (in_array($char, [" ", "\t", "\r", "\n", "\f", "\v"]))
			continue;

		switch ($char) {
			case '[':
			case '{':
				$indent_count++;
				$result .= $char . $wrap . str_repeat($indent, $indent_count);
				break;
			case ']':
			case '}':
				$indent_count--;
				$result = trim($result) . $wrap . str_repeat($indent, $indent_count) . $char;
				break;
			case ':':
				$result .= $char . ' ';
				break;
			case ',':
				$result .= $char . $wrap . str_repeat($indent, $indent_count);
				break;
			case '"':
				$result  .= $char;
				$inquote = !$inquote;
				break;
			default:
				$result .= $char;
		}
	}

	return $result;
}

/**
 * 删除url中query指定参数
 *
 * @param  string           $url
 * @param  string|string[]  $keys
 *
 * @return string
 */
function url_query_del ($url, $keys = '*') {
	$u = parse_url($url);
	if (!isset($u['query']) || $u['query'] === '') return $url;

	parse_str($u['query'], $arr);
	if ($keys === '*') {
		unset($u['query']);
	} else {
		if (!is_array($keys)) $keys = [$keys];
		$keys       = array_fill_keys($keys, '');
		$arr        = array_diff_key($arr, $keys);
		$u['query'] = arr2params($arr, true);
	}

	return build_url($u);
}

/**
 * url编码
 *
 * @param  string       $url      url完整链接
 * @param  true|string  $en_char  对查询参数query编码的单字节字符
 *
 * @return string
 */
function url_encode ($url, $en_char = true) {
	if (false === $arr = parse_url($url))
		return $url;

	return build_url($arr, $en_char);
}

/**
 * 构建url
 *
 * @param  array        $url_info
 * @param  true|string  $en_char  对查询参数query编码的单字节字符
 *
 * @return string
 */
function build_url ($url_info, $en_char = true) {
	$ret = '';
	if (!is_array($url_info)) return $ret;

	extract($url_info);

	if (isset($scheme)) $ret .= $scheme . ':';
	if (isset($host)) {
		$ret .= '//';
		if (isset($user)) {
			$ret .= str_encode($user, '+:@');
			if (isset($pass)) $ret .= ':' . str_encode($user, '+@');
			$ret .= '@';
		}
		$ret .= str_encode($host);
	}
	if (isset($port)) $ret .= ':' . $port;

	if (isset($path)) {
		$arr  = explode('/', $path);
		$arr  = array_map('str_encode', $arr);
		$path = '/' . implode('/', $arr);
		$ret  .= preg_replace('#//+#', '/', $path);
	}
	if (isset($query) && $query !== '') $ret .= '?' . str_encode($query, $en_char);
	if (isset($fragment) && $fragment !== '') $ret .= '#' . str_encode($fragment);

	return $ret;
}

/**
 * 字符串url编码
 *
 * @param  string       $str
 * @param  true|string  $en_char  需要编码的单字节字符
 *
 * @return string
 */
function str_encode ($str, $en_char = true) {
	if ($str === null || $str === '') return '';

	if ($en_char === true) $en_char = '+';
	else $en_char = trim($en_char);

	if ($en_char !== '') {
		$keys = array_unique(str_split($en_char));
		$vals = array_map('rawurlencode', $keys);
		$str  = str_replace($keys, $vals, $str);
	}

	return preg_replace_callback('/[^\x21-\x7e]/u', function ($m) {
		return rawurlencode($m[0]);
	}, $str);
}

/**
 * base64安全编码
 *
 * @param  string  $str
 *
 * @return string
 */
function b64_encode ($str) {
	return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($str));
}

/**
 * base64安全解码
 *
 * @param  string  $str
 *
 * @return string
 */
function b64_decode ($str) {
	return base64_decode(str_replace(['-', '_'], ['+', '/'], $str));
}

/**
 * @param  string  $msg   字符串信息
 * @param  bool    $exit  是否终止程序
 * @param  bool    $eol   是否换行
 *
 * @return void
 */
function e ($msg, $exit = false, $eol = false) {
	$msg = str_replace(
		['{datetime}', '{date}', '{time}'],
		[date('Y-m-d H:i:s'), date('Y-m-d'), date('H:i:s')],
		$msg
	);
	echo $eol ? $msg . PHP_EOL : $msg;
	if ($exit) exit();
}

/**
 * @param  string                               $msg   字符串信息
 * @param  int                                  $type  类型（-1：头，0：中间，1：尾）
 * @param  array{eol:bool,style:int,exit:bool}  $opt   额外参数
 *
 * @return void
 */
function eLine ($msg, $type = 0, $opt = ['eol' => true, 'style' => 0, 'exit' => false]) {
	$opt    = array_merge(['eol' => true, 'style' => 0, 'exit' => false], $opt);
	$styles = [
		['┌', '├', '└'],
		['┏', '┣', '┗'],
		['╔', '╠', '╚'],
		['╭', '│', '╰'],
	];
	if (array_key_exists($opt['style'], $styles) && array_key_exists($type + 1, $styles[$opt['style']]))
		$msg = $styles[$opt['style']][$type + 1] . ' ' . $msg;

	e($msg, $opt['exit'], $opt['eol']);
}

/**
 * 获取当前url对应的目录url
 *
 * @param  bool  $host    是否需要域名
 * @param  bool  $scheme  是否需要协议头
 *
 * @return string
 */
function request_dir ($host = true, $scheme = true) {
	$ret = '';
	if ($scheme) $ret .= @$_SERVER['HTTPS'] === 1 || @$_SERVER['HTTPS'] === 'on' || @$_SERVER['SERVER_PORT'] === 443 ? 'https://' : 'http://';
	if ($host) $ret .= @$_SERVER['HTTP_HOST'];
	$ret .= preg_replace('/\/+[^\/?]*(\?.*)?$/', '', @$_SERVER['REQUEST_URI']) . '/';

	return $ret;
}

/**
 * 填充
 *
 * @param  string  $str
 * @param  int     $blocksize
 *
 * @return string
 */
function pkcs5_pad ($str, $blocksize) {
	$pad = $blocksize - (strlen($str) % $blocksize);

	return $str . str_repeat(chr($pad), $pad);
}

/**
 * 去填充
 *
 * @param  string  $str
 *
 * @return string
 */
function pkcs5_unpad ($str) {
	$pad = ord(substr($str, -1));
	if ($pad > strlen($str)) return false;

	if (strspn($str, chr($pad), strlen($str) - $pad) != $pad)
		return false;

	return substr($str, 0, -1 * $pad);
}

/*
 * 字符串 => 字节数组
 *
 * @param  int    $str   要复制的数组的长度
 *
 * @return array
 */
function getBytes ($str) {
	return array_values(unpack("c*", $str));
}

/**
 * 字节数组 => 字符串
 *
 * @param  array  $bytes
 *
 * @return false|string
 */
function bytes2Str ($bytes) {
	return pack("c*", ...$bytes);
}

/**
 * int转字节byte
 *
 * @param  int  $num
 *
 * @return false|int
 */
function int2byte ($num) {
	$arr = unpack('c*', pack('l', $num));

	return reset($arr);
}

/**
 * 处理int32系统位溢出溢出
 *
 * @param  int  $num
 *
 * @return false|int
 */
function intval32 ($num) {
	$arr = unpack("l", pack("l", $num));

	return reset($arr);
}

/**
 * 逻辑右移 (a >>> n)
 *
 * @param  int  $a
 * @param  int  $n
 *
 * @return int
 */
function uright ($a, $n) {
	return (intval($a) & 0xFFFFFFFF) >> $n;
}

function charCodeAt ($str, $index) {
	$char = mb_substr($str, $index, 1, 'UTF-8');
	if (mb_check_encoding($char, 'UTF-8')) {
		$ret = mb_convert_encoding($char, 'UTF-32BE', 'UTF-8');

		return hexdec(bin2hex($ret));
	} else return null;
}

/**
 * 判断是否标准json字符串
 *
 * @param  string  $str          json字符串
 * @param  bool    $associative  是否解析为数组
 *
 * @return false|mixed
 */
function is_json ($str, $associative = false) {
	if ($str === null || '' === ($str = trim($str)) || !preg_match('/(^[{\[]|[}\[]$)/', $str))
		return false;

	$obj = json_decode($str, $associative, 512, JSON_BIGINT_AS_STRING);

	return null !== $obj && json_last_error() === JSON_ERROR_NONE ? $obj : false;
}

/**
 * 取post数据
 *
 * @param  string  $key      键名
 * @param  mixed   $default  默认值
 *
 * @return mixed|null
 */
function p ($key, $default = null) {
	return val($_POST, $key, $default);
}

/**
 * 取get数据
 *
 * @param  string  $key      键名
 * @param  mixed   $default  默认值
 *
 * @return mixed|null
 */
function g ($key, $default = null) {
	return val($_GET, $key, $default);
}

/**
 * 取request数据
 *
 * @param  string  $key      键名
 * @param  mixed   $default  默认值
 *
 * @return mixed|null
 */
function r ($key, $default = null) {
	return val($_REQUEST, $key, $default);
}

/**
 * @param  array   $var      键值对数组
 * @param  string  $key      键名
 * @param  mixed   $default  默认值
 *
 * @return bool|float|int|mixed|string|null
 */
function val ($var, $key, $default = null) {
	if (!isset($var[$key]) || $var[$key] === '') return $default;

	if (is_float($default)) return floatval($var[$key]);
	if (is_bool($default)) return boolval($var[$key]);
	if (is_int($default)) return intval($var[$key]);

	return $var[$key];
}

/**
 * @return \Classes\Curl
 */
function curl () {
	return \Classes\Curl::init();
}